package org.playground.booking.service.impl;

import com.sun.javafx.binding.StringFormatter;
import javassist.NotFoundException;
import org.apache.log4j.Logger;
import org.playground.booking.dao.IBookingDao;
import org.playground.booking.dao.IConfigUrlDao;
import org.playground.booking.dao.IWechatMerchantAccountDao;
import org.playground.booking.dto.WechatPaymentDto;
import org.playground.booking.entity.Booking;
import org.playground.booking.entity.UrlConfig;
import org.playground.booking.entity.WechatMerchantAccount;
import org.playground.booking.enums.UrlConfigType;
import org.playground.booking.service.IWechatService;
import org.playground.booking.utils.CommonUtils;
import org.playground.booking.utils.HttpUtils;
import org.playground.booking.utils.PlaygroundBookingErrMsg;
import org.playground.booking.utils.PlaygroundBookingException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import org.w3c.dom.CharacterData;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import java.io.*;
import java.lang.reflect.Field;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.*;

@Service
public class WechatServiceImpl implements IWechatService{
    private static final Logger LOGGER = Logger.getLogger(WechatServiceImpl.class);
    private static final String GRANT_TYPE="authorization_code";
    private static final String TRADE_TYPE="";
    private static final String SIGN_TYPE="MD5";

    @Autowired
    private IWechatMerchantAccountDao wechatMerchantAccountDao;
    @Autowired
    private IBookingDao bookingDao;
    @Autowired
    private IConfigUrlDao configUrlDao;

    public String exchangeOpenid(String appId, String exchangeCode) throws PlaygroundBookingException, NotFoundException {
        if(exchangeCode==null||exchangeCode==""){
            return "{\"error\":\"no exchange code specified\"}";
        }


        WechatMerchantAccount wechatMerchantAccount = wechatMerchantAccountDao.readByAppId(appId);

        if(wechatMerchantAccount!=null){

            //TODO to put into database
            String secret = wechatMerchantAccount.getAppsecret();
            UrlConfig wechatExchangeOpenID = configUrlDao.getUrlByType(UrlConfigType.WECHAT_EXCHANGE_OPENID);
            if(wechatExchangeOpenID==null|| StringUtils.isEmpty(wechatExchangeOpenID.getUrl())){
                throw new NotFoundException("The url for exchanging the openid is not defined in the datbase");
            }
            String url = StringFormatter.format(wechatExchangeOpenID.getUrl()+"?appid=%s&secret=%s&js_code=%s&grant_type="+GRANT_TYPE,
                    appId,
                    secret,
                    exchangeCode).toString();

            String result = HttpUtils.httpGet(url);

            return result;

        }else{
            return "{\"error\":\"no account corresponding to +"+appId+"\"}";
        }
    }

    public String applyPayment(String appId, Long orderId, String openId, int amount) throws IOException, PlaygroundBookingException, SAXException, ParserConfigurationException, IllegalAccessException {

        WechatPaymentDto paymentRequest = new WechatPaymentDto();
        Booking booking = bookingDao.readById(orderId);

        if(booking==null){
            LOGGER.error(PlaygroundBookingErrMsg.UNINITIATED_BOOKING.getErrorMessage()+" : "+orderId);
            throw new PlaygroundBookingException(PlaygroundBookingErrMsg.UNINITIATED_BOOKING);
        }
        //get merchant account
        WechatMerchantAccount merchantAccount = wechatMerchantAccountDao.readByAppId(appId);

        if(merchantAccount==null){
            LOGGER.error(PlaygroundBookingErrMsg.NOT_EXISTING_MERCHANT_ACCOUNT.getErrorMessage()+" : "+appId);
            throw new PlaygroundBookingException(PlaygroundBookingErrMsg.NOT_EXISTING_MERCHANT_ACCOUNT);
        }
        UrlConfig wechatNotifyConfig = configUrlDao.getUrlByType(UrlConfigType.WECHAT_NOTIF);
        UrlConfig wechatPaymentConfig = configUrlDao.getUrlByType(UrlConfigType.WECHAT_UNITY_ORDER);
        //prepare data

        paymentRequest.setAppid(appId);
        paymentRequest.setOpenid(openId);
        paymentRequest.setMerch_id(merchantAccount.getMerchantid());
        paymentRequest.setBody("体育场-场馆预订");
        paymentRequest.setNonce_str(getNounceString(32));
        paymentRequest.setSpbill_create_ip(getServerIp());
        paymentRequest.setOut_trade_no(String.valueOf(orderId));
        paymentRequest.setTotal_fee(String.valueOf(amount));
        paymentRequest.setTrade_type(TRADE_TYPE);
        paymentRequest.setSign_type(SIGN_TYPE);
        paymentRequest.setNotify_url(wechatNotifyConfig.getUrl());

        paymentRequest.setSign(getSign(paymentRequest,merchantAccount.getAppsecret()));


        //submit formular
        String result = HttpUtils.httpPost(wechatPaymentConfig.getUrl(),CommonUtils.objectToXml(paymentRequest),"application/xml");

        //get prepayId
        return getPrepayId(result);
    }

    private String getPrepayId(String wechatResponse) throws ParserConfigurationException, IOException, SAXException, PlaygroundBookingException {
        DocumentBuilder db = DocumentBuilderFactory.newInstance().newDocumentBuilder();
        InputStream is = new ByteArrayInputStream(wechatResponse.getBytes(StandardCharsets.UTF_8.name()));
        Document doc = db.parse(is);
        NodeList nl = doc.getElementsByTagName("return_code");
        if(CommonUtils.getCDataValue(nl.item(0))=="success"){
            NodeList ppnl =doc.getElementsByTagName("prepay_id");
            return CommonUtils.getCDataValue(ppnl.item(0));
        }else{

            NodeList errorNl = doc.getElementsByTagName("err_code");
            NodeList errorMsgNl = doc.getElementsByTagName("err_code_des");

            String errorCode = CommonUtils.getCDataValue(errorNl.item(0));
            String errorMsg = CommonUtils.getCDataValue(errorMsgNl.item(0));

            throw new PlaygroundBookingException(errorCode+":"+errorMsg);
        }
    }

    private String getNounceString(int length){

        String base = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
        StringBuilder sb = new StringBuilder();
        Random random = new Random(base.length()-1);

        for(int i=0;i<length;i++){
            int index = random.nextInt(length-1);
            sb.append(base.toCharArray()[index]);
        }

        return sb.toString();
    }

    private String getServerIp() throws UnknownHostException {
        return InetAddress.getLocalHost().getHostAddress();
    }

    private String getSign(WechatPaymentDto request,String secretKey)   {

       List<String> result = new ArrayList<String>();
       Map<String,Object> requestMap = new HashMap<String,Object>();
       Field[] fields = request.getClass().getDeclaredFields();
       for(Field field : fields){
         if(field.getName()!="sign"){
            result.add(field.getName());
             try {
                 requestMap.put(field.getName(),field.get(request));
             } catch (IllegalAccessException e) {
                 LOGGER.error(e.getMessage());
             }
         }
       }
       Collections.sort(result, new Comparator<String>() {
           @Override
           public int compare(String o1, String o2) {
               return o1.compareTo(o2);
           }
       });

        StringBuilder sb = new StringBuilder();
        for(String key : result){
            sb.append(key+""+requestMap.get(key)+"&");
        }
        sb.append(secretKey);

        byte[] signBytes = new byte[0];
        try {
            signBytes = MessageDigest.getInstance("MD5").digest(sb.toString().getBytes());
        } catch (NoSuchAlgorithmException e) {
            LOGGER.error(e.getMessage());
        }
        String sign = String.valueOf(signBytes).toUpperCase();
       return sign;
    }
}
